package pl.edu.agh.solid;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.TreeSet;
import java.util.UUID;



public class Solid {

	final UUID id = UUID.randomUUID();
	Face [] faces;
	//faces (numbered by the array's indices); 
	//faces  is the basic list: edges and vertices are determined on the basis of faces
	
	SolidEdge [] edges;  
	SolidVertex [] vertices;
	
	boolean finalized = false;
	
	
	public Solid() {

		faces = new Face[0];
		
	}
	
	
	
	public UUID getId() {
		return id;
	}



	public Face[] getFaces() {
		return faces;
	}



	public SolidEdge[] getEdges() {
		return edges;
	}



	public SolidVertex[] getVertices() {
		return vertices;
	}



	public boolean isFinalized() {
		return finalized;
	}



	public void addFace(float [][] f) {
		
		if (finalized)
			try {
				throw new Exception();
			} catch (Exception e) {
				System.out.println("Solid already finalized. can't add a face");
				e.printStackTrace();
				return;
			}
		
		Face [] dst = new Face[faces.length + 1];
		System.arraycopy(faces, 0, dst, 0, faces.length);
		dst[dst.length-1] = new Face(f);
		
		
		
	}
	
	
	/*
	 * Warning: it is assumed that all have been already added to this
	 */
	public void finalizeSolid() {
		
		finalized = true;
		
		
		// Determine solid vertices
		ListOfVerices vrefs = getAllVertices();
		
		vertices = new SolidVertex [vrefs.size()];
		
		int count = 0;
		for(VertexRef ref : vrefs) {
			
			vertices[count++] = new SolidVertex(ref.v, ref.ownerFaces);
			
		}

		// Determine solid edges
		ArrayList<SolidEdge> solidedgelist = new ArrayList<>();
		for(VertexRef ref1 : vrefs) {
			for(VertexRef ref2 : vrefs) {
				
				TreeSet<Integer> comm = VertexRef.commonOwners(ref1, ref2);
				if (comm.size()<2) continue;
				if (comm.size()>2) {
					System.out.println("Strange : two vertices share "+comm.size()+" common owners.");
					System.exit(1010);
				}
				
				solidedgelist.add(new SolidEdge(comm.first(), comm.last(), new FaceEdge(ref1.v.getCoords(), ref2.v.getCoords())));
				
			}
			
		}
		
		edges = new SolidEdge[solidedgelist.size()];
		for(int k=0; k<solidedgelist.size(); k++) {
			edges[k] = solidedgelist.get(k);
		}

		
	}
	
	static class VertexRef {
		Vertex v = null;
		TreeSet<Integer> ownerFaces = null;
		
		public VertexRef(Vertex w, int owner) {
			
			this.v = w;
			this.ownerFaces = new TreeSet<>();
			this.ownerFaces.add(owner);
			
		}

		static TreeSet<Integer> commonOwners(VertexRef ref1, VertexRef ref2) {

			
			    TreeSet<Integer> tmp = new TreeSet<Integer>();
			    for (Integer x : ref1.ownerFaces)
			      if (ref2.ownerFaces.contains(x))
			        tmp.add(x);
			    return tmp;
						
		}
		
		
		public void add(int owner) {
			
			this.ownerFaces.add(owner);
			
		}
		
		public boolean contains(Vertex w) {
			return this.v.equals(w);
		}		
	}
	
	class ListOfVerices extends ArrayList<VertexRef> {
		
		private static final long serialVersionUID = 7109736974416226659L;

		VertexRef getVertexRef(Vertex v) {
			
			for(VertexRef ref : this) {
				if (ref.contains(v)) return ref;
			}			
			return null;
		}

		
		public void add(Vertex w, int owner) {
			
			VertexRef vref = this.getVertexRef(w);
			
			if (vref != null) vref.add(owner);
			else {
				vref = new VertexRef(w, owner);
				this.add(vref);
			}
		}
	}
	
	private ListOfVerices getAllVertices() {

		ListOfVerices ret = new ListOfVerices();
		
		for(int i = 0; i< faces.length; i++) {
			
			for(FaceEdge fe : faces[i].getEdges()) {
				
				ret.add(fe.getVertices()[0],i);
				ret.add(fe.getVertices()[1],i);
			}
		}
		
		return ret;
	}



	/**
	 * Creates a solid from polygon
	 * @param coords polygon 2D coordinates
	 * @return
	 */
	static float dh = (float) 0.001;
	public static Solid elevate(float[][] coords) {

		Solid ret = new Solid();
		
		float [][] coordsBottom = new float[coords.length][3];
		float [][] coordsTop = new float[coords.length][3];
		
		
		//Bottom & top
		
		for(int k=0; k< coords.length; k++) {
			
			coordsBottom[k][0] = coords[k][0];
			coordsBottom[k][1] = coords[k][1];
			coordsBottom[k][2] = 0;

			coordsTop[k][0] = coords[k][0];
			coordsTop[k][1] = coords[k][1];
			coordsTop[k][2] = dh;
			
		}

		ret.addFace(coordsBottom);
		ret.addFace(coordsTop);
		
		// Sidewalls: coords.length rectangles
		
		float [][][] sideWalls = new float[coords.length][4][3];
		
		for(int k=0; k< coords.length; k++) {
			
			sideWalls[k][0] = coordsTop[k];
			sideWalls[k][1] = coordsTop[(k+1)%coords.length];			
			sideWalls[k][2] = coordsBottom[(k+1)%coords.length];
			sideWalls[k][3] = coordsBottom[k];
			
			ret.addFace(sideWalls[k]);
		}
		
		ret.finalizeSolid();
		
		return ret;
	}
		
	
}
